package com.freedom.sql.jdbc.utils;

/**
 * 
 * @author zhiqiang.liu
 * @2016年6月20日
 * @Email: 837500869@qq.com
 */
import org.apache.log4j.Logger;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.freedom.sql.jdbc.bind.Bind;
import com.freedom.sql.jdbc.bind.Link;
import com.freedom.sql.jdbc.pool.socket.MySocket;
import com.freedom.sql.jdbc.pool.socket.SocketFactory;

import java.net.Socket;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.TreeSet;

import org.apache.commons.pool2.BasePooledObjectFactory;
import org.apache.commons.pool2.impl.BaseObjectPoolConfig;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.apache.log4j.LogManager;

public class MyJsonProperties {
	private static final Logger logger = LogManager.getLogger(MyJsonProperties.class);

	// 以下为全局需要
	private static MyJsonProperties myProperties = null;// 全局单例变量，一开始就存在

	static {// 静态块里，只加载一次
		JSONObject jsonObject = null;
		try {
			String jsonString = FileUtils.readFile(MyConstants.CONFIG_FILE);
			LoggerUtils.debug(logger, jsonString);
			jsonObject = JSON.parseObject(jsonString);
		} catch (Exception e) {
			LoggerUtils.error(logger, e.toString() + " fail to read config file " + MyConstants.CONFIG_FILE);
			System.exit(-1);
		}
		LoggerUtils.debug(logger, "succeed to read config file " + MyConstants.CONFIG_FILE);
		// 提取常规值->新对象
		JSONObject serverObject = jsonObject.getJSONObject(MyConstants.SERVER);
		if (null == serverObject) {
			LoggerUtils.error(logger, "fail to get serverObject from " + MyConstants.CONFIG_FILE);
			System.exit(-1);
		}
		int netty_port = serverObject.getIntValue(MyConstants.NETTY_PORT);
		int netty_boss = serverObject.getIntValue(MyConstants.NETTY_BOSS);
		int netty_worker = Runtime.getRuntime().availableProcessors()
				* serverObject.getIntValue(MyConstants.NETTY_WORKER);//
		int consumer_worker = Runtime.getRuntime().availableProcessors()
				* serverObject.getIntValue(MyConstants.CONSUMER_WORKER);//
		myProperties = new MyJsonProperties(netty_port, netty_boss, netty_worker, consumer_worker);
		// 提取username/password->赋值
		JSONObject authObject = jsonObject.getJSONObject(MyConstants.AUTH);
		if (null == authObject) {
			LoggerUtils.error(logger, "fail to get authObject from " + MyConstants.CONFIG_FILE);
			System.exit(-1);
		}
		String username = authObject.getString(MyConstants.USERNAME);
		String password = authObject.getString(MyConstants.PASSWORD);
		myProperties.setUsername(username);
		myProperties.setPassword(password);
		LoggerUtils.debug(logger, "succeed to create my properties object ");

		// 解析数据库&表
		HashMap<String, ArrayList<String>> databases = new HashMap<String, ArrayList<String>>();
		String dbName;
		String tableName;
		JSONObject dbObject;
		JSONObject tableObject;
		JSONArray dbObjects = jsonObject.getJSONArray(MyConstants.DB);
		if (null == dbObjects || dbObjects.size() <= 0) {
			LoggerUtils.error(logger, "fail to get db Objects from " + MyConstants.CONFIG_FILE);
			System.exit(-1);
		}
		for (int i = 0; i < dbObjects.size(); i++) {
			dbObject = (JSONObject) dbObjects.get(i);
			dbName = dbObject.getString(MyConstants.NAME);
			if (null == dbName) {
				continue;
			}
			dbName = dbName.trim();
			if (dbName.length() <= 0) {
				continue;
			}
			// 已经拿到了数据库的名字
			// 还需要拿表的名字
			ArrayList<String> tables = new ArrayList<String>();
			{
				JSONArray tableObjects = dbObject.getJSONArray(MyConstants.TABLE);
				if (null == tableObjects) {
					continue;
				}
				for (int index = 0; index < tableObjects.size(); index++) {
					tableObject = (JSONObject) tableObjects.get(index);
					tableName = tableObject.getString(MyConstants.NAME);
					if (null != tableName && tableName.trim().length() > 0) {
						tables.add(tableName.trim());
					}
				}
			}
			// if (null != dbName && dbName.length() > 0 && null != tables &&
			// tables.size() > 0) {
			databases.put(dbName, tables);// 添加到HashMap
			// }
			LoggerUtils.info(logger, "db---" + dbName + " tables: " + tables);
		}
		myProperties.setDatabases(databases);// 保存起来
		// 数据库连接池
		HashMap<String, GenericObjectPool<MySocket>> physicalDataSources = new HashMap<String, GenericObjectPool<MySocket>>();
		try {
			// 是采用druid连接池还是commons-pools连接池?
			// 1)druid连接池的用法如下:
			// http://lj6684.iteye.com/blog/1770093-基本设置
			// http://www.iteye.com/magazines/90 - 更详细的参数
			// 2) 决定选择commons-pools连接池,druid在本项目中只充当sql解析器
			JSONArray dataSourceConfigObjects = jsonObject.getJSONArray(MyConstants.DATASOURCE);
			for (int i = 0; i < dataSourceConfigObjects.size(); i++) {
				JSONObject config = dataSourceConfigObjects.getJSONObject(i);
				String key = config.getString(MyConstants.NAME);
				//
				//
				// 0)构造factory
				String ip = config.getString(MyConstants.IP);
				int port = config.getIntValue(MyConstants.PORT);
				username = config.getString(MyConstants.USERNAME);// _username
				password = config.getString(MyConstants.PASSWORD);// password
				BasePooledObjectFactory<MySocket> poolFactory = new SocketFactory(ip, port, username, password);
				// 0)构造poolConfig
				GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
				poolConfig.setJmxEnabled(false);// 是否启用统计,这里不启用
				// 1)系统启动
				poolConfig.setMinIdle(config.getIntValue(MyConstants.MINIDLE));// 启动时会有线程负责初始化这么多个连接,一个线程负责
				// 2)获取对象相关的配置
				// 获取一个object是1)直接borrow->create->等待队列可用+最大等待时长(最大等待时长的borrow)
				poolConfig.setFairness(BaseObjectPoolConfig.DEFAULT_FAIRNESS);// 从队列里borrow时加锁，默认值为false,保持默认,后台为一个ReentrantLock锁
				poolConfig.setMaxTotal(config.getIntValue(MyConstants.MAXTOTAL));// 是否可以直接create
				poolConfig.setBlockWhenExhausted(config.getBooleanValue(MyConstants.BLOCKWHENEXHAUSTED));
				poolConfig.setMaxWaitMillis(config.getLongValue(MyConstants.MAXWAIT));
				poolConfig.setTestOnBorrow(config.getBooleanValue(MyConstants.TESTONBORROW));// borrow的时候，是否需要测试->调用validateObject
				poolConfig.setTestOnCreate(config.getBooleanValue(MyConstants.TESTONCREATE));// create的时候，是否需要测试->调用validateObject
				//
				// 3)归还对象时相关的配置
				poolConfig.setMaxIdle(config.getIntValue(MyConstants.MAXIDLE));// 是否可以归还此对象?
				poolConfig.setLifo(false);// 一旦确定归还，放入队列头还是尾?
				poolConfig.setTestOnReturn(config.getBooleanValue(MyConstants.TESTONRETURN));// return的时候，是否需要测试->调用validateObject
				//
				// 4)定期驱逐策略[比较重要，及时驱逐劣币就靠这个了]
				poolConfig.setEvictionPolicyClassName(BaseObjectPoolConfig.DEFAULT_EVICTION_POLICY_CLASS_NAME);// 默认值
				poolConfig.setTimeBetweenEvictionRunsMillis(
						config.getIntValue(MyConstants.TIMEBETWEENEVICTIONRUNSMILLIS));// evict驱逐线程的间隔周期
				poolConfig.setNumTestsPerEvictionRun(BaseObjectPoolConfig.DEFAULT_NUM_TESTS_PER_EVICTION_RUN);// 每次进行驱逐测试的个数
				poolConfig.setMinEvictableIdleTimeMillis(config.getLongValue(MyConstants.MINEVICTABLEIDLETIMEMILLIS));// 如果超过这段时间没有被使用，则无条件被强制驱逐
				poolConfig
						.setSoftMinEvictableIdleTimeMillis(BaseObjectPoolConfig.DEFAULT_MIN_EVICTABLE_IDLE_TIME_MILLIS);// 如果当前idle的个数>minIdle,并且idle时长超过这个阈值(默认为30分钟)，则立刻被驱逐
				poolConfig.setTestWhileIdle(config.getBooleanValue(MyConstants.TESTWHILEIDLE));// idle时，通过了evict测试,判断是否需要被测试->调用validateObject
				//
				// 3)组合
				GenericObjectPool<MySocket> socketPool = new GenericObjectPool<MySocket>(poolFactory, poolConfig);
				// 4)保存
				physicalDataSources.put(key, socketPool);
			} // for 结束
			myProperties.setDataSources(physicalDataSources);
			physicalDataSources = null;// 切断关联关系
		} catch (Exception e) {
			LoggerUtils.error(logger,
					"fail to init Druid Data Source " + MyConstants.CONFIG_FILE + " due to :" + e.toString());
			System.exit(-1);
		}

		// 解析bind函数
		HashMap<String, Bind> binds = new HashMap<String, Bind>();
		try {
			JSONArray bindObjects = jsonObject.getJSONArray(MyConstants.BIND);
			if (null == bindObjects) {
				throw new Exception("no bind specified");
			}
			for (int index = 0; index < bindObjects.size(); index++) {
				JSONObject bindObject = (JSONObject) bindObjects.get(index);
				String logicDB = bindObject.getString(MyConstants.LOGICDB);
				String logicTable = bindObject.getString(MyConstants.LOGICTABLE);
				// 然后获取link
				TreeSet<Link> writeSet = new TreeSet<Link>(); // 可写
				TreeSet<Link> readSet = new TreeSet<Link>();// 可读
				JSONArray dataSourceArray = bindObject.getJSONArray(MyConstants.DATASOURCE);
				for (int k = 0; k < dataSourceArray.size(); k++) {
					JSONObject dataSource = dataSourceArray.getJSONObject(k);
					String name = dataSource.getString(MyConstants.NAME);
					String physicalDB = dataSource.getString(MyConstants.PHYSICALDB);
					String physicalTable = dataSource.getString(MyConstants.PHYSICALTABLE);
					int weight = dataSource.getIntValue(MyConstants.WEIGHT);
					String action = dataSource.getString(MyConstants.ACTION);
					// 构造一个对象
					Link bind = new Link(name, physicalDB, physicalTable, weight);
					if (-1 != action.indexOf("r")) {
						readSet.add(bind);
					}
					if (-1 != action.indexOf("w")) {
						writeSet.add(bind);
					}
				}
				// 排好序了,输出到List里
				ArrayList<Link> writeList = new ArrayList<Link>();
				Iterator<Link> iterator = writeSet.iterator();
				while (iterator.hasNext()) {
					writeList.add((Link) iterator.next());
				}
				ArrayList<Link> readList = new ArrayList<Link>();
				iterator = readSet.iterator();
				while (iterator.hasNext()) {
					readList.add((Link) iterator.next());
				}
				// 万事俱备，可以开始构造了
				Bind bind = new Bind(logicDB, logicTable, readList, writeList);
				binds.put(StringUtils.union(logicDB, logicTable), bind);
			}
		} catch (Exception e) {
			LoggerUtils.error(logger,
					"fail to parse bind[db<->dataSource] from " + MyConstants.CONFIG_FILE + " due to :" + e.toString());
			System.exit(-1);
		}
		myProperties.setBinds(binds);
		// print it
		// Set<String> keys = binds.keySet();
		// for (String key : keys) {
		// Bind bind = binds.get(key);
		// LoggerUtils.info(logger, bind.toString());
		// }

	}// static块结束

	public static MyJsonProperties getInstance() {
		return myProperties;
	}

	// 私有属性开始//////////////////////////////////////////////////////////////////
	// netty
	private int nettyPort;
	private int nettyBoss;
	private int nettyWorker;
	// consumer worker
	private int consumerWorker;
	// used for login
	private String username;
	private String password;

	// connection pool[implementd by Druid]
	private HashMap<String, Object> connectionPools = new HashMap<String, Object>();

	public HashMap<String, Object> getConnectionPools() {
		return connectionPools;
	}

	public void setConnectionPools(HashMap<String, Object> connectionPools) {
		this.connectionPools = connectionPools;
	}

	// 存储数据库
	private HashMap<String, ArrayList<String>> databases;

	// 存储数据源
	private HashMap<String, GenericObjectPool<MySocket>> dataSources;

	// 存储映射关系
	private HashMap<String, Bind> binds;

	public HashMap<String, Bind> getBinds() {
		return binds;
	}

	public void setBinds(HashMap<String, Bind> binds) {
		this.binds = binds;
	}

	public HashMap<String, GenericObjectPool<MySocket>> getDataSources() {
		return dataSources;
	}

	public void setDataSources(HashMap<String, GenericObjectPool<MySocket>> dataSources) {
		this.dataSources = dataSources;
	}

	private MyJsonProperties() {// 私有方法，保证单例
	}

	public HashMap<String, ArrayList<String>> getDatabases() {
		return databases;
	}

	public void setDatabases(HashMap<String, ArrayList<String>> databases) {
		this.databases = databases;
	}

	private MyJsonProperties(int np, int nboss, int nworker, int cworker) {
		// used by netty
		this.nettyPort = np;
		this.nettyBoss = nboss;
		this.nettyWorker = nworker;
		this.consumerWorker = cworker;
	}

	public String getUsername() {
		return username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	public int getConsumerWorker() {
		return this.consumerWorker;
	}

	public int getNettyPort() {
		return nettyPort;
	}

	public int getNettyBoss() {
		return nettyBoss;
	}

	public int getNettyWorker() {
		return nettyWorker;
	}

	public String toString() {
		StringBuilder strBuilder = new StringBuilder("\n");
		strBuilder.append(MyConstants.NETTY_PORT).append(": ").append(nettyPort).append("\n");
		strBuilder.append(MyConstants.NETTY_BOSS).append(": ").append(nettyBoss).append("\n");
		strBuilder.append(MyConstants.NETTY_WORKER).append(": ").append(nettyWorker).append("\n");
		strBuilder.append(MyConstants.CONSUMER_WORKER).append(": ").append(consumerWorker).append("\n");
		strBuilder.append("user").append(": ").append(this.username).append("\n");
		strBuilder.append("password").append(": ").append(this.password).append("\n");
		return strBuilder.toString();
	}

	// 测试
	public static void main(String[] args) {
		// just for test
		MyJsonProperties property = MyJsonProperties.getInstance();
		logger.debug(property.toString());
	}
}
